import asyncio
import sys
import os
import time
import unittest
from unittest.mock import MagicMock, ANY
# Mock MinioClient before importing modules that use it
from unittest.mock import patch
import numpy as np
from types import ModuleType, SimpleNamespace

from fastapi.responses import StreamingResponse

# Patch environment variables before any imports that might use them
os.environ.setdefault('MINIO_ENDPOINT', 'http://localhost:9000')
os.environ.setdefault('MINIO_ACCESS_KEY', 'minioadmin')
os.environ.setdefault('MINIO_SECRET_KEY', 'minioadmin')
os.environ.setdefault('MINIO_REGION', 'us-east-1')
os.environ.setdefault('MINIO_DEFAULT_BUCKET', 'test-bucket')

# Mock boto3 before importing the module under test
boto3_mock = MagicMock()
sys.modules['boto3'] = boto3_mock

# Mock nexent modules before importing modules that use them
def _create_package_mock(name: str) -> MagicMock:
    pkg = MagicMock()
    pkg.__path__ = []  # Mark as package for importlib
    pkg.__spec__ = SimpleNamespace(name=name, submodule_search_locations=[])
    return pkg


nexent_mock = _create_package_mock('nexent')
sys.modules['nexent'] = nexent_mock
sys.modules['nexent.core'] = _create_package_mock('nexent.core')
sys.modules['nexent.core.agents'] = _create_package_mock('nexent.core.agents')
sys.modules['nexent.core.agents.agent_model'] = MagicMock()
sys.modules['nexent.core.models'] = _create_package_mock('nexent.core.models')
sys.modules['nexent.core.models.embedding_model'] = MagicMock()
sys.modules['nexent.core.models.stt_model'] = MagicMock()
sys.modules['nexent.core.nlp'] = _create_package_mock('nexent.core.nlp')
sys.modules['nexent.core.nlp.tokenizer'] = MagicMock()
sys.modules['nexent.vector_database'] = _create_package_mock('nexent.vector_database')
vector_db_base_module = ModuleType('nexent.vector_database.base')


class _VectorDatabaseCore:
    """Lightweight stand-in for the real VectorDatabaseCore for import-time typing."""
    pass


vector_db_base_module.VectorDatabaseCore = _VectorDatabaseCore
sys.modules['nexent.vector_database.base'] = vector_db_base_module
sys.modules['nexent.vector_database.elasticsearch_core'] = MagicMock()
# Mock nexent.storage module and its submodules before any imports
sys.modules['nexent.storage'] = _create_package_mock('nexent.storage')
storage_factory_module = MagicMock()
storage_config_module = MagicMock()
# Create mock classes/functions that will be imported
MinIOStorageConfigMock = MagicMock()
MinIOStorageConfigMock.validate = lambda self: None
storage_factory_module.create_storage_client_from_config = MagicMock()
storage_factory_module.MinIOStorageConfig = MinIOStorageConfigMock
storage_config_module.MinIOStorageConfig = MinIOStorageConfigMock
sys.modules['nexent.storage.storage_client_factory'] = storage_factory_module
sys.modules['nexent.storage.minio_config'] = storage_config_module

# Mock specific classes that are imported
sys.modules['nexent.core.agents.agent_model'].ToolConfig = MagicMock()
sys.modules['nexent.core.models.stt_model'].STTConfig = MagicMock()
sys.modules['nexent.core.models.stt_model'].STTModel = MagicMock()
sys.modules['nexent.core.models.tts_model'] = MagicMock()
sys.modules['nexent.core.models.tts_model'].TTSConfig = MagicMock()
sys.modules['nexent.core.models.tts_model'].TTSModel = MagicMock()

# Patch storage factory and MinIO config validation to avoid errors during initialization
# These patches must be started before any imports that use MinioClient
storage_client_mock = MagicMock()
# Configure storage_client_mock.delete_file to return tuple (True, None)
storage_client_mock.delete_file.return_value = (True, None)
minio_client_mock = MagicMock()
# Configure default return values for minio_client_mock methods
minio_client_mock.delete_file.return_value = (True, None)
minio_client_mock.storage_config = MagicMock()
minio_client_mock.storage_config.default_bucket = 'test-bucket'
# Set _storage_client to storage_client_mock so MinioClient.delete_file works correctly
minio_client_mock._storage_client = storage_client_mock
patch('nexent.storage.storage_client_factory.create_storage_client_from_config', return_value=storage_client_mock).start()
patch('nexent.storage.minio_config.MinIOStorageConfig.validate', lambda self: None).start()
patch('backend.database.client.MinioClient', return_value=minio_client_mock).start()
patch('backend.database.client.minio_client', minio_client_mock).start()
# Patch attachment_db.minio_client to use the same mock
# This ensures delete_file and other methods work correctly
patch('backend.database.attachment_db.minio_client', minio_client_mock).start()

# Apply the patches before importing the module being tested
with patch('botocore.client.BaseClient._make_api_call'), \
        patch('elasticsearch.Elasticsearch', return_value=MagicMock()):
    from backend.services.vectordatabase_service import ElasticSearchService, check_knowledge_base_exist_impl


def _accurate_search_impl(request, vdb_core):
    start_time = time.time()
    if not request.query or not request.query.strip():
        raise Exception("Search query cannot be empty")
    if not request.index_names:
        raise Exception("At least one index name is required")

    results = vdb_core.accurate_search(
        index_names=request.index_names,
        query=request.query,
        top_k=request.top_k
    )
    end_time = time.time()
    query_time_ms = (end_time - start_time) * 1000

    return {
        "results": results,
        "total": len(results),
        "query_time_ms": query_time_ms
    }


def _semantic_search_impl(request, vdb_core):
    start_time = time.time()
    results = vdb_core.semantic_search(
        index_names=request.index_names,
        query=request.query,
        top_k=request.top_k
    )
    end_time = time.time()
    query_time_ms = (end_time - start_time) * 1000

    return {
        "results": results,
        "total": len(results),
        "query_time_ms": query_time_ms
    }




class TestElasticSearchService(unittest.TestCase):
    def setUp(self):
        """
        Set up test environment before each test.

        This method initializes a fresh ElasticSearchService instance
        and prepares mock objects for the ES core and embedding model
        that will be used across test cases.
        """
        self.es_service = ElasticSearchService()
        self.mock_vdb_core = MagicMock()
        self.mock_vdb_core.embedding_model = MagicMock()
        self.mock_vdb_core.embedding_dim = 768

        # Patch get_embedding_model for all tests
        self.get_embedding_model_patcher = patch(
            'backend.services.vectordatabase_service.get_embedding_model')
        self.mock_get_embedding = self.get_embedding_model_patcher.start()
        self.mock_embedding = MagicMock()
        self.mock_embedding.embedding_dim = 768
        self.mock_embedding.model = "test-model"
        self.mock_get_embedding.return_value = self.mock_embedding

        ElasticSearchService.accurate_search = staticmethod(
            _accurate_search_impl)
        ElasticSearchService.semantic_search = staticmethod(
            _semantic_search_impl)

    def tearDown(self):
        """Clean up resources after each test."""
        self.get_embedding_model_patcher.stop()
        if hasattr(ElasticSearchService, 'accurate_search'):
            del ElasticSearchService.accurate_search
        if hasattr(ElasticSearchService, 'semantic_search'):
            del ElasticSearchService.semantic_search

    @patch('backend.services.vectordatabase_service.create_knowledge_record')
    def test_create_index_success(self, mock_create_knowledge):
        """
        Test successful index creation.

        This test verifies that:
        1. The index is created when it doesn't already exist
        2. The vector index is properly configured with the correct embedding dimension
        3. A knowledge record is created for the new index
        4. The method returns a success status
        """
        # Setup
        self.mock_vdb_core.check_index_exists.return_value = False
        self.mock_vdb_core.create_index.return_value = True
        mock_create_knowledge.return_value = True

        # Execute
        result = ElasticSearchService.create_index(
            index_name="test_index",
            embedding_dim=768,
            vdb_core=self.mock_vdb_core,
            user_id="test_user",
            tenant_id="test_tenant"  # Added explicit tenant_id
        )

        # Assert
        self.assertEqual(result["status"], "success")
        self.mock_vdb_core.check_index_exists.assert_called_once_with(
            "test_index")
        self.mock_vdb_core.create_index.assert_called_once_with(
            "test_index", embedding_dim=768)
        mock_create_knowledge.assert_called_once()

    @patch('backend.services.vectordatabase_service.create_knowledge_record')
    def test_create_index_already_exists(self, mock_create_knowledge):
        """
        Test index creation when the index already exists.

        This test verifies that:
        1. An Exception with status code 500 is raised when the index already exists
        2. The exception message contains "already exists"
        3. No knowledge record is created
        """
        # Setup
        self.mock_vdb_core.check_index_exists.return_value = True

        # Execute and Assert
        with self.assertRaises(Exception) as context:
            ElasticSearchService.create_index(
                index_name="test_index",
                embedding_dim=768,
                vdb_core=self.mock_vdb_core,
                user_id="test_user"
            )

        # Check the exception message
        self.assertIn("already exists", str(context.exception))
        mock_create_knowledge.assert_not_called()

    @patch('backend.services.vectordatabase_service.create_knowledge_record')
    def test_create_index_failure(self, mock_create_knowledge):
        """
        Test index creation failure.

        This test verifies that:
        1. An Exception with status code 500 is raised when index creation fails
        2. The exception message contains "Failed to create index"
        3. No knowledge record is created
        """
        # Setup
        self.mock_vdb_core.check_index_exists.return_value = False
        self.mock_vdb_core.create_index.return_value = False

        # Execute and Assert
        with self.assertRaises(Exception) as context:
            ElasticSearchService.create_index(
                index_name="test_index",
                embedding_dim=768,
                vdb_core=self.mock_vdb_core,
                user_id="test_user",
                tenant_id="test_tenant"  # Added explicit tenant_id
            )

        self.assertIn("Failed to create index", str(context.exception))
        mock_create_knowledge.assert_not_called()

    @patch('backend.services.vectordatabase_service.delete_knowledge_record')
    def test_delete_index_success(self, mock_delete_knowledge):
        """
        Test successful index deletion.

        This test verifies that:
        1. The index is successfully deleted from Elasticsearch
        2. The corresponding knowledge record is deleted
        3. The method returns a success status
        """
        # Setup
        self.mock_vdb_core.delete_index.return_value = True
        mock_delete_knowledge.return_value = True

        # Execute
        async def run_test():
            result = await ElasticSearchService.delete_index(
                index_name="test_index",
                vdb_core=self.mock_vdb_core,
                user_id="test_user"
            )

            # Assert
            self.assertEqual(result["status"], "success")
            self.mock_vdb_core.delete_index.assert_called_once_with(
                "test_index")
            mock_delete_knowledge.assert_called_once()

        asyncio.run(run_test())

    @patch('backend.services.vectordatabase_service.delete_knowledge_record')
    def test_delete_index_failure(self, mock_delete_knowledge):
        """
        Test index deletion failure.

        This test verifies that:
        1. When index deletion fails, the method still proceeds with knowledge record deletion
        2. The method returns success status if knowledge record deletion succeeds
        """
        # Setup
        self.mock_vdb_core.delete_index.return_value = False
        mock_delete_knowledge.return_value = True

        # Execute
        async def run_test():
            result = await ElasticSearchService.delete_index(
                index_name="test_index",
                vdb_core=self.mock_vdb_core,
                user_id="test_user"
            )

            # Assert
            self.assertEqual(result["status"], "success")
            self.mock_vdb_core.delete_index.assert_called_once_with(
                "test_index")
            mock_delete_knowledge.assert_called_once()

        asyncio.run(run_test())

    @patch('backend.services.vectordatabase_service.delete_knowledge_record')
    def test_delete_index_knowledge_record_failure(self, mock_delete_knowledge):
        """
        Test deletion when the index is deleted but knowledge record deletion fails.

        This test verifies that:
        1. When Elasticsearch index is deleted successfully but knowledge record deletion fails
        2. An Exception with status code 500 is raised
        3. The exception message contains "Error deleting knowledge record"
        """
        # Setup
        self.mock_vdb_core.delete_index.return_value = True
        mock_delete_knowledge.return_value = False

        # Execute and Assert
        async def run_test():
            with self.assertRaises(Exception) as context:
                await ElasticSearchService.delete_index(
                    index_name="test_index",
                    vdb_core=self.mock_vdb_core,
                    user_id="test_user"
                )

            self.assertIn("Error deleting knowledge record",
                          str(context.exception))

        asyncio.run(run_test())

    @patch('backend.services.vectordatabase_service.get_knowledge_info_by_tenant_id')
    def test_list_indices_without_stats(self, mock_get_knowledge):
        """
        Test listing indices without including statistics.

        This test verifies that:
        1. The method retrieves indices matching the pattern
        2. The correct number of indices is returned
        3. No statistics are requested when include_stats is False
        """
        # Setup
        self.mock_vdb_core.get_user_indices.return_value = ["index1", "index2"]
        mock_get_knowledge.return_value = [
            {"index_name": "index1", "embedding_model_name": "test-model"},
            {"index_name": "index2", "embedding_model_name": "test-model"}
        ]

        # Execute
        result = ElasticSearchService.list_indices(
            pattern="*",
            include_stats=False,
            tenant_id="test_tenant",  # Now required parameter
            user_id="test_user",      # New required parameter
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(len(result["indices"]), 2)
        self.assertEqual(result["count"], 2)
        self.mock_vdb_core.get_user_indices.assert_called_once_with("*")
        mock_get_knowledge.assert_called_once_with(tenant_id="test_tenant")

    @patch('backend.services.vectordatabase_service.get_knowledge_info_by_tenant_id')
    def test_list_indices_with_stats(self, mock_get_knowledge):
        """
        Test listing indices with statistics included.

        This test verifies that:
        1. The method retrieves indices matching the pattern
        2. Statistics for each index are also retrieved
        3. Both indices and their stats are included in the response
        """
        # Setup
        self.mock_vdb_core.get_user_indices.return_value = ["index1", "index2"]
        self.mock_vdb_core.get_indices_detail.return_value = {
            "index1": {"base_info": {"doc_count": 10, "embedding_model": "test-model"}},
            "index2": {"base_info": {"doc_count": 20, "embedding_model": "test-model"}}
        }
        mock_get_knowledge.return_value = [
            {"index_name": "index1", "embedding_model_name": "test-model"},
            {"index_name": "index2", "embedding_model_name": "test-model"}
        ]

        # Execute
        result = ElasticSearchService.list_indices(
            pattern="*",
            include_stats=True,
            tenant_id="test_tenant",  # Now required parameter
            user_id="test_user",      # New required parameter
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(len(result["indices"]), 2)
        self.assertEqual(result["count"], 2)
        self.assertEqual(len(result["indices_info"]), 2)
        self.mock_vdb_core.get_user_indices.assert_called_once_with("*")
        self.mock_vdb_core.get_indices_detail.assert_called_once_with(
            ["index1", "index2"])
        mock_get_knowledge.assert_called_once_with(tenant_id="test_tenant")

    @patch('backend.services.vectordatabase_service.get_knowledge_info_by_tenant_id')
    @patch('backend.services.vectordatabase_service.delete_knowledge_record')
    def test_list_indices_removes_stale_pg_records(self, mock_delete_knowledge, mock_get_info):
        """
        Test that list_indices deletes PostgreSQL records whose indices are missing in Elasticsearch.
        """
        self.mock_vdb_core.get_user_indices.return_value = ["es_index"]
        mock_get_info.return_value = [
            {"index_name": "dangling_index", "embedding_model_name": "model-A"}
        ]

        result = ElasticSearchService.list_indices(
            pattern="*",
            include_stats=False,
            tenant_id="tenant-1",
            user_id="user-1",
            vdb_core=self.mock_vdb_core
        )

        mock_delete_knowledge.assert_called_once_with(
            {"index_name": "dangling_index", "user_id": "user-1"}
        )
        self.assertEqual(result["indices"], [])
        self.assertEqual(result["count"], 0)

    @patch('backend.services.vectordatabase_service.get_knowledge_info_by_tenant_id')
    def test_list_indices_stats_defaults_when_missing(self, mock_get_info):
        """
        Test list_indices include_stats path when Elasticsearch returns no stats for an index.
        """
        self.mock_vdb_core.get_user_indices.return_value = ["index1"]
        mock_get_info.return_value = [
            {"index_name": "index1", "embedding_model_name": "model-A"}
        ]
        self.mock_vdb_core.get_indices_detail.return_value = {}

        result = ElasticSearchService.list_indices(
            pattern="*",
            include_stats=True,
            tenant_id="tenant-1",
            user_id="user-1",
            vdb_core=self.mock_vdb_core
        )

        self.assertEqual(result["indices"], ["index1"])
        self.assertEqual(result["indices_info"][0]["name"], "index1")
        self.assertEqual(result["indices_info"][0]["stats"], {})

    @patch('backend.services.vectordatabase_service.update_model_name_by_index_name')
    @patch('backend.services.vectordatabase_service.get_knowledge_info_by_tenant_id')
    def test_list_indices_backfills_missing_model_names(self, mock_get_info, mock_update_model):
        """
        Test that list_indices updates database records when embedding_model_name is missing.
        """
        self.mock_vdb_core.get_user_indices.return_value = ["index1"]
        mock_get_info.return_value = [
            {"index_name": "index1", "embedding_model_name": None}
        ]
        self.mock_vdb_core.get_indices_detail.return_value = {
            "index1": {"base_info": {"embedding_model": "text-embedding-ada-002"}}
        }

        result = ElasticSearchService.list_indices(
            pattern="*",
            include_stats=True,
            tenant_id="tenant-1",
            user_id="user-1",
            vdb_core=self.mock_vdb_core
        )

        mock_update_model.assert_called_once_with(
            "index1", "text-embedding-ada-002", "tenant-1", "user-1"
        )
        self.assertEqual(result["count"], 1)
        self.assertEqual(result["indices"][0], "index1")

    @patch('backend.services.vectordatabase_service.get_knowledge_info_by_tenant_id')
    def test_list_indices_stats_surfaces_elasticsearch_errors(self, mock_get_info):
        """
        Test that list_indices propagates Elasticsearch errors while fetching stats.
        """
        self.mock_vdb_core.get_user_indices.return_value = ["index1"]
        mock_get_info.return_value = [
            {"index_name": "index1", "embedding_model_name": "model-A"}
        ]
        self.mock_vdb_core.get_indices_detail.side_effect = Exception(
            "503 Service Unavailable"
        )

        with self.assertRaises(Exception) as context:
            ElasticSearchService.list_indices(
                pattern="*",
                include_stats=True,
                tenant_id="tenant-1",
                user_id="user-1",
                vdb_core=self.mock_vdb_core
            )

        self.assertIn("503 Service Unavailable", str(context.exception))

    @patch('backend.services.vectordatabase_service.get_knowledge_info_by_tenant_id')
    def test_list_indices_stats_keeps_non_stat_fields(self, mock_get_info):
        """
        Test that list_indices preserves all stats fields returned by ElasticSearchCore.
        """
        self.mock_vdb_core.get_user_indices.return_value = ["index1"]
        mock_get_info.return_value = [
            {"index_name": "index1", "embedding_model_name": "model-A"}
        ]
        detailed_stats = {
            "index1": {
                "base_info": {
                    "doc_count": 42,
                    "process_source": "Unstructured",
                    "embedding_model": "text-embedding-3-large"
                },
                "search_performance": {"avg_time": 12.3}
            }
        }
        self.mock_vdb_core.get_indices_detail.return_value = detailed_stats

        result = ElasticSearchService.list_indices(
            pattern="*",
            include_stats=True,
            tenant_id="tenant-1",
            user_id="user-1",
            vdb_core=self.mock_vdb_core
        )

        self.assertEqual(len(result["indices_info"]), 1)
        self.assertEqual(result["indices_info"][0]["stats"], detailed_stats["index1"])

    def test_vectorize_documents_success(self):
        """
        Test successful document indexing.

        This test verifies that:
        1. Documents are properly indexed when the index exists
        2. The indexing operation returns the correct count of indexed documents
        3. The response contains proper success status and document counts
        4. Documents with various metadata fields are handled correctly
        """
        # Setup
        self.mock_vdb_core.check_index_exists.return_value = True
        self.mock_vdb_core.vectorize_documents.return_value = 2
        mock_embedding_model = MagicMock()
        mock_embedding_model.model = "test-model"

        test_data = [
            {
                "metadata": {
                    "title": "Test Document",
                    "languages": ["en"],
                    "author": "Test Author",
                    "date": "2023-01-01",
                    "creation_date": "2023-01-01T12:00:00"
                },
                "path_or_url": "test_path",
                "content": "Test content",
                "source_type": "file",
                "file_size": 1024,
                "filename": "test.txt"
            },
            {
                "metadata": {
                    "title": "Test Document 2"
                },
                "path_or_url": "test_path2",
                "content": "Test content 2"
            }
        ]

        # Execute
        result = ElasticSearchService.index_documents(
            index_name="test_index",
            data=test_data,
            vdb_core=self.mock_vdb_core,
            embedding_model=mock_embedding_model
        )

        # Assert
        self.assertTrue(result["success"])
        self.assertEqual(result["total_indexed"], 2)
        self.assertEqual(result["total_submitted"], 2)
        self.mock_vdb_core.vectorize_documents.assert_called_once()

    def test_vectorize_documents_empty_data(self):
        """
        Test document indexing with empty data.

        This test verifies that:
        1. When no documents are provided, the method handles it gracefully
        2. No documents are indexed when the data list is empty
        3. The response correctly indicates success with zero documents
        """
        # Setup
        test_data = []
        mock_embedding_model = MagicMock()

        # Execute
        result = ElasticSearchService.index_documents(
            index_name="test_index",
            data=test_data,
            vdb_core=self.mock_vdb_core,
            embedding_model=mock_embedding_model
        )

        # Assert
        self.assertTrue(result["success"])
        self.assertEqual(result["total_indexed"], 0)
        self.assertEqual(result["total_submitted"], 0)
        self.mock_vdb_core.vectorize_documents.assert_not_called()

    def test_vectorize_documents_create_index(self):
        """
        Test document indexing when the index doesn't exist.

        This test verifies that:
        1. When the index doesn't exist, it's created automatically
        2. After creating the index, documents are indexed successfully
        3. The response contains the correct status and document counts
        """
        # Setup
        self.mock_vdb_core.check_index_exists.return_value = False
        self.mock_vdb_core.create_index.return_value = True
        self.mock_vdb_core.vectorize_documents.return_value = 1
        mock_embedding_model = MagicMock()
        test_data = [
            {
                "metadata": {"title": "Test"},
                "path_or_url": "test_path",
                "content": "Test content"
            }
        ]

        # Execute
        with patch('backend.services.vectordatabase_service.ElasticSearchService.create_index') as mock_create_index:
            mock_create_index.return_value = {"status": "success"}
            result = ElasticSearchService.index_documents(
                index_name="test_index",
                data=test_data,
                vdb_core=self.mock_vdb_core,
                embedding_model=mock_embedding_model
            )

        # Assert
        self.assertTrue(result["success"])
        self.assertEqual(result["total_indexed"], 1)
        mock_create_index.assert_called_once()

    def test_vectorize_documents_indexing_error(self):
        """
        Test document indexing when an error occurs during indexing.

        This test verifies that:
        1. When an error occurs during indexing, an appropriate exception is raised
        2. The exception has the correct status code (500)
        3. The exception message contains "Error during indexing"
        """
        # Setup
        self.mock_vdb_core.check_index_exists.return_value = True
        self.mock_vdb_core.vectorize_documents.side_effect = Exception(
            "Indexing error")
        mock_embedding_model = MagicMock()
        test_data = [
            {
                "metadata": {"title": "Test"},
                "path_or_url": "test_path",
                "content": "Test content"
            }
        ]

        # Execute and Assert
        with self.assertRaises(Exception) as context:
            ElasticSearchService.index_documents(
                index_name="test_index",
                data=test_data,
                vdb_core=self.mock_vdb_core,
                embedding_model=mock_embedding_model
            )

        self.assertIn("Error during indexing", str(context.exception))

    @patch('backend.services.vectordatabase_service.get_all_files_status')
    def test_list_files_without_chunks(self, mock_get_files_status):
        """
        Test listing files without including document chunks.

        This test verifies that:
        1. Files indexed in Elasticsearch are retrieved correctly
        2. Files being processed (from Redis) are included in the results
        3. Files from both sources are combined in the response
        4. The status of each file is correctly set (COMPLETED or PROCESSING)
        """
        # Setup
        self.mock_vdb_core.get_documents_detail.return_value = [
            {
                "path_or_url": "file1",
                "filename": "file1.txt",
                "file_size": 1024,
                "create_time": "2023-01-01T12:00:00"
            }
        ]
        mock_get_files_status.return_value = {
            "file2": {"state": "PROCESSING", "latest_task_id": "task123"}}

        # Execute
        async def run_test():
            return await ElasticSearchService.list_files(
                index_name="test_index",
                include_chunks=False,
                vdb_core=self.mock_vdb_core
            )

        result = asyncio.run(run_test())

        # Assert
        self.assertEqual(len(result["files"]), 2)
        self.assertEqual(result["files"][0]["status"], "COMPLETED")
        self.assertEqual(result["files"][1]["status"], "PROCESSING")
        self.mock_vdb_core.get_documents_detail.assert_called_once_with(
            "test_index")

    @patch('backend.services.vectordatabase_service.get_all_files_status')
    def test_list_files_with_chunks(self, mock_get_files_status):
        """
        Test listing files with document chunks included.

        This test verifies that:
        1. Files indexed in Elasticsearch are retrieved correctly
        2. Document chunks for each file are retrieved using msearch
        3. The chunks are included in the file details
        4. The chunk count is correctly calculated
        """
        # Setup
        self.mock_vdb_core.get_documents_detail.return_value = [
            {
                "path_or_url": "file1",
                "filename": "file1.txt",
                "file_size": 1024,
                "create_time": "2023-01-01T12:00:00"
            }
        ]
        mock_get_files_status.return_value = {}

        # Mock multi_search response
        msearch_response = {
            'responses': [
                {
                    'hits': {
                        'hits': [
                            {
                                '_source': {
                                    'id': 'doc1',
                                    'title': 'Title 1',
                                    'content': 'Content 1',
                                    'create_time': '2023-01-01T12:00:00'
                                }
                            }
                        ]
                    }
                }
            ]
        }
        self.mock_vdb_core.multi_search.return_value = msearch_response

        # Execute
        async def run_test():
            return await ElasticSearchService.list_files(
                index_name="test_index",
                include_chunks=True,
                vdb_core=self.mock_vdb_core
            )

        result = asyncio.run(run_test())

        # Assert
        self.assertEqual(len(result["files"]), 1)
        self.assertEqual(len(result["files"][0]["chunks"]), 1)
        self.assertEqual(result["files"][0]["chunk_count"], 1)
        self.mock_vdb_core.multi_search.assert_called_once()

    @patch('backend.services.vectordatabase_service.get_all_files_status')
    def test_list_files_msearch_error(self, mock_get_files_status):
        """
        Test listing files when msearch encounters an error.

        This test verifies that:
        1. When msearch fails, the method handles the error gracefully
        2. Files are still returned without chunks
        3. Chunk count is set to 0 for affected files
        4. The overall operation doesn't fail due to msearch errors
        """
        # Setup
        self.mock_vdb_core.get_documents_detail.return_value = [
            {
                "path_or_url": "file1",
                "filename": "file1.txt",
                "file_size": 1024,
                "create_time": "2023-01-01T12:00:00"
            }
        ]
        mock_get_files_status.return_value = {}

        # Mock msearch error
        self.mock_vdb_core.client.msearch.side_effect = Exception(
            "MSSearch Error")

        # Execute
        async def run_test():
            return await ElasticSearchService.list_files(
                index_name="test_index",
                include_chunks=True,
                vdb_core=self.mock_vdb_core
            )

        result = asyncio.run(run_test())

        # Assert
        self.assertEqual(len(result["files"]), 1)
        self.assertEqual(len(result["files"][0]["chunks"]), 0)
        self.assertEqual(result["files"][0]["chunk_count"], 0)

    @patch('backend.services.vectordatabase_service.delete_file')
    def test_delete_documents(self, mock_delete_file):
        """
        Test document deletion by path or URL.

        This test verifies that:
        1. Documents with the specified path or URL are deleted
        2. The response contains a success status
        """
        # Setup
        self.mock_vdb_core.delete_documents.return_value = 5
        # Configure delete_file to return a success response
        mock_delete_file.return_value = {"success": True, "object_name": "test_path"}

        # Execute
        result = ElasticSearchService.delete_documents(
            index_name="test_index",
            path_or_url="test_path",
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(result["status"], "success")
        self.assertEqual(result["deleted_minio"], True)
        # Verify that delete_documents was called with correct parameters
        self.mock_vdb_core.delete_documents.assert_called_once_with(
            "test_index", "test_path")
        # Verify that delete_file was called with the correct path
        mock_delete_file.assert_called_once_with("test_path")

    def test_accurate_search(self):
        """
        Test accurate (keyword-based) search functionality.

        This test verifies that:
        1. The accurate_search method correctly calls the core search implementation
        2. Search results are properly formatted in the response
        3. The response includes total count and query time
        4. The search is performed across the specified indices
        """
        # Setup
        search_request = MagicMock()
        search_request.index_names = ["test_index"]
        search_request.query = "test query"
        search_request.top_k = 10

        self.mock_vdb_core.accurate_search.return_value = [
            {
                "document": {"title": "Doc1", "content": "Content1"},
                "score": 0.95,
                "index": "test_index"
            }
        ]

        # Execute
        result = ElasticSearchService.accurate_search(
            request=search_request,
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(len(result["results"]), 1)
        self.assertEqual(result["total"], 1)
        self.assertTrue("query_time_ms" in result)
        self.mock_vdb_core.accurate_search.assert_called_once_with(
            index_names=["test_index"], query="test query", top_k=10
        )

    def test_accurate_search_empty_query(self):
        """
        Test accurate search with an empty query.

        This test verifies that:
        1. When the query is empty or consists only of whitespace, an exception is raised
        2. The exception has the correct status code (500)
        3. The exception message contains "Search query cannot be empty"
        """
        # Setup
        search_request = MagicMock()
        search_request.index_names = ["test_index"]
        search_request.query = "   "  # Empty query
        search_request.top_k = 10

        # Execute and Assert
        with self.assertRaises(Exception) as context:
            ElasticSearchService.accurate_search(
                request=search_request,
                vdb_core=self.mock_vdb_core
            )

        self.assertIn("Search query cannot be empty", str(context.exception))

    def test_accurate_search_no_indices(self):
        """
        Test accurate search with no indices specified.

        This test verifies that:
        1. When no indices are specified, an exception is raised
        2. The exception has the correct status code (500)
        3. The exception message contains "At least one index name is required"
        """
        # Setup
        search_request = MagicMock()
        search_request.index_names = []  # No indices
        search_request.query = "test query"
        search_request.top_k = 10

        # Execute and Assert
        with self.assertRaises(Exception) as context:
            ElasticSearchService.accurate_search(
                request=search_request,
                vdb_core=self.mock_vdb_core
            )

        self.assertIn("At least one index name is required",
                      str(context.exception))

    def test_semantic_search(self):
        """
        Test semantic (embedding-based) search functionality.

        This test verifies that:
        1. The semantic_search method correctly calls the core search implementation
        2. Search results are properly formatted in the response
        3. The response includes total count and query time
        4. The search is performed across the specified indices
        """
        # Setup
        search_request = MagicMock()
        search_request.index_names = ["test_index"]
        search_request.query = "test query"
        search_request.top_k = 10

        # Create a mock response directly on the vdb_core instance
        self.mock_vdb_core.semantic_search.return_value = [
            {
                "document": {"title": "Doc1", "content": "Content1"},
                "score": 0.85,
                "index": "test_index"
            }
        ]

        # Execute
        result = ElasticSearchService.semantic_search(
            request=search_request,
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(len(result["results"]), 1)
        self.assertEqual(result["total"], 1)
        self.assertTrue("query_time_ms" in result)
        self.mock_vdb_core.semantic_search.assert_called_once_with(
            index_names=["test_index"], query="test query", top_k=10
        )

    def test_search_hybrid_success(self):
        """
        Test hybrid search (combining semantic and accurate search).

        This test verifies that:
        1. The search_hybrid method correctly calls the core search implementation
        2. The weight parameter for balancing semantic and accurate search is passed correctly
        3. Search results include individual scores for both semantic and accurate searches
        4. The response contains the expected structure with results, total, and timing information
        """
        # Setup
        self.mock_vdb_core.hybrid_search.return_value = [
            {
                "document": {"title": "Doc1", "content": "Content1"},
                "score": 0.90,
                "index": "test_index",
                "scores": {"accurate": 0.85, "semantic": 0.95}
            }
        ]

        # Execute
        result = ElasticSearchService.search_hybrid(
            index_names=["test_index"],
            query="test query",
            tenant_id="test_tenant",
            top_k=10,
            weight_accurate=0.5,
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(len(result["results"]), 1)
        self.assertEqual(result["total"], 1)
        self.assertTrue("query_time_ms" in result)
        self.assertEqual(result["results"][0]["score"], 0.90)
        self.assertEqual(result["results"][0]["index"], "test_index")
        self.assertEqual(result["results"][0]["score_details"]["accurate"], 0.85)
        self.assertEqual(result["results"][0]["score_details"]["semantic"], 0.95)
        self.mock_vdb_core.hybrid_search.assert_called_once_with(
            index_names=["test_index"],
            query_text="test query",
            embedding_model=self.mock_embedding,
            top_k=10,
            weight_accurate=0.5
        )

    def test_search_hybrid_missing_tenant_id(self):
        """Test search_hybrid raises ValueError when tenant_id is missing."""
        with self.assertRaises(ValueError) as context:
            ElasticSearchService.search_hybrid(
                index_names=["test_index"],
                query="test query",
                tenant_id="",
                top_k=10,
                weight_accurate=0.5,
                vdb_core=self.mock_vdb_core
            )
        self.assertIn("Tenant ID is required", str(context.exception))

    def test_search_hybrid_empty_query(self):
        """Test search_hybrid raises ValueError when query is empty."""
        with self.assertRaises(ValueError) as context:
            ElasticSearchService.search_hybrid(
                index_names=["test_index"],
                query="   ",
                tenant_id="test_tenant",
                top_k=10,
                weight_accurate=0.5,
                vdb_core=self.mock_vdb_core
            )
        self.assertIn("Query text is required", str(context.exception))

    def test_search_hybrid_no_indices(self):
        """Test search_hybrid raises ValueError when no indices provided."""
        with self.assertRaises(ValueError) as context:
            ElasticSearchService.search_hybrid(
                index_names=[],
                query="test query",
                tenant_id="test_tenant",
                top_k=10,
                weight_accurate=0.5,
                vdb_core=self.mock_vdb_core
            )
        self.assertIn("At least one index name is required", str(context.exception))

    def test_search_hybrid_invalid_top_k(self):
        """Test search_hybrid raises ValueError when top_k is invalid."""
        with self.assertRaises(ValueError) as context:
            ElasticSearchService.search_hybrid(
                index_names=["test_index"],
                query="test query",
                tenant_id="test_tenant",
                top_k=0,
                weight_accurate=0.5,
                vdb_core=self.mock_vdb_core
            )
        self.assertIn("top_k must be greater than 0", str(context.exception))

    def test_search_hybrid_invalid_weight(self):
        """Test search_hybrid raises ValueError when weight_accurate is invalid."""
        with self.assertRaises(ValueError) as context:
            ElasticSearchService.search_hybrid(
                index_names=["test_index"],
                query="test query",
                tenant_id="test_tenant",
                top_k=10,
                weight_accurate=1.5,
                vdb_core=self.mock_vdb_core
            )
        self.assertIn("weight_accurate must be between 0 and 1", str(context.exception))

    def test_search_hybrid_no_embedding_model(self):
        """Test search_hybrid raises ValueError when embedding model is not configured."""
        # Stop the mock to test the real get_embedding_model
        self.get_embedding_model_patcher.stop()
        try:
            with patch('backend.services.vectordatabase_service.get_embedding_model', return_value=None):
                with self.assertRaises(ValueError) as context:
                    ElasticSearchService.search_hybrid(
                        index_names=["test_index"],
                        query="test query",
                        tenant_id="test_tenant",
                        top_k=10,
                        weight_accurate=0.5,
                        vdb_core=self.mock_vdb_core
                    )
                self.assertIn("No embedding model configured", str(context.exception))
        finally:
            self.get_embedding_model_patcher.start()

    def test_search_hybrid_exception(self):
        """Test search_hybrid handles exceptions from vdb_core."""
        self.mock_vdb_core.hybrid_search.side_effect = Exception("Search failed")
        
        with self.assertRaises(Exception) as context:
            ElasticSearchService.search_hybrid(
                index_names=["test_index"],
                query="test query",
                tenant_id="test_tenant",
                top_k=10,
                weight_accurate=0.5,
                vdb_core=self.mock_vdb_core
            )
        self.assertIn("Error executing hybrid search", str(context.exception))

    def test_search_hybrid_weight_accurate_boundary_values(self):
        """Test search_hybrid with different weight_accurate values to ensure line 1146 is covered."""
        # Test with weight_accurate = 0.0 (semantic only)
        self.mock_vdb_core.hybrid_search.return_value = [
            {
                "document": {"title": "Doc1", "content": "Content1"},
                "score": 0.90,
                "index": "test_index",
            }
        ]

        result = ElasticSearchService.search_hybrid(
            index_names=["test_index"],
            query="test query",
            tenant_id="test_tenant",
            top_k=10,
            weight_accurate=0.0,
            vdb_core=self.mock_vdb_core
        )
        self.assertEqual(len(result["results"]), 1)
        self.mock_vdb_core.hybrid_search.assert_called_with(
            index_names=["test_index"],
            query_text="test query",
            embedding_model=self.mock_embedding,
            top_k=10,
            weight_accurate=0.0
        )

        # Test with weight_accurate = 1.0 (accurate only)
        self.mock_vdb_core.hybrid_search.reset_mock()
        result = ElasticSearchService.search_hybrid(
            index_names=["test_index"],
            query="test query",
            tenant_id="test_tenant",
            top_k=10,
            weight_accurate=1.0,
            vdb_core=self.mock_vdb_core
        )
        self.mock_vdb_core.hybrid_search.assert_called_with(
            index_names=["test_index"],
            query_text="test query",
            embedding_model=self.mock_embedding,
            top_k=10,
            weight_accurate=1.0
        )

        # Test with weight_accurate = 0.3 (more semantic)
        self.mock_vdb_core.hybrid_search.reset_mock()
        result = ElasticSearchService.search_hybrid(
            index_names=["test_index"],
            query="test query",
            tenant_id="test_tenant",
            top_k=10,
            weight_accurate=0.3,
            vdb_core=self.mock_vdb_core
        )
        self.mock_vdb_core.hybrid_search.assert_called_with(
            index_names=["test_index"],
            query_text="test query",
            embedding_model=self.mock_embedding,
            top_k=10,
            weight_accurate=0.3
        )

    def test_health_check_healthy(self):
        """
        Test health check when Elasticsearch is healthy.

        This test verifies that:
        1. The health check correctly reports a healthy status when Elasticsearch is available
        2. The response includes the connection status and indices count
        3. The health_check method returns without raising exceptions
        """
        # Setup
        self.mock_vdb_core.get_user_indices.return_value = ["index1", "index2"]

        # Execute
        result = ElasticSearchService.health_check(vdb_core=self.mock_vdb_core)

        # Assert
        self.assertEqual(result["status"], "healthy")
        self.assertEqual(result["elasticsearch"], "connected")
        self.assertEqual(result["indices_count"], 2)

    def test_health_check_unhealthy(self):
        """
        Test health check when Elasticsearch is unhealthy.

        This test verifies that:
        1. When Elasticsearch is unavailable, an exception is raised
        2. The exception has the correct status code (500)
        3. The exception message contains "Health check failed"
        """
        # Setup
        self.mock_vdb_core.get_user_indices.side_effect = Exception(
            "Connection error")

        # Execute and Assert
        with self.assertRaises(Exception) as context:
            ElasticSearchService.health_check(vdb_core=self.mock_vdb_core)

        self.assertIn("Health check failed", str(context.exception))


    @patch('database.model_management_db.get_model_by_model_id')
    def test_summary_index_name(self, mock_get_model_by_model_id):
        """
        Test generating a summary for an index.

        This test verifies that:
        1. Random documents are retrieved for summarization
        2. The summary generation stream is properly initialized using Map-Reduce approach
        3. A StreamingResponse object is returned for streaming the summary tokens
        """
        # Setup
        mock_get_model_by_model_id.return_value = {
            'api_key': 'test_api_key',
            'base_url': 'https://api.test.com',
            'model_name': 'test-model',
            'model_repo': 'test-repo'
        }

        # Mock the new Map-Reduce functions
        with patch('utils.document_vector_utils.process_documents_for_clustering') as mock_process_docs, \
             patch('utils.document_vector_utils.kmeans_cluster_documents') as mock_cluster, \
             patch('utils.document_vector_utils.summarize_clusters_map_reduce') as mock_summarize, \
             patch('utils.document_vector_utils.merge_cluster_summaries') as mock_merge, \
             patch('database.model_management_db.get_model_by_model_id') as mock_get_model_internal:

            # Mock return values
            mock_process_docs.return_value = (
                {"doc1": {"chunks": [{"content": "test content"}]}},  # document_samples
                {"doc1": np.array([0.1, 0.2, 0.3])}  # doc_embeddings
            )
            mock_cluster.return_value = {"doc1": 0}  # clusters
            mock_summarize.return_value = {0: "Test cluster summary"}  # cluster_summaries
            mock_merge.return_value = "Final merged summary"  # final_summary
            mock_get_model_internal.return_value = {
                'api_key': 'test_api_key',
                'base_url': 'https://api.test.com',
                'model_name': 'test-model'
            }

            # Execute
            async def run_test():
                result = await self.es_service.summary_index_name(
                    index_name="test_index",
                    batch_size=1000,
                    vdb_core=self.mock_vdb_core,
                    language='en',
                    model_id=1,
                    tenant_id="test_tenant"
                )

                # Consume part of the stream to trigger the generator function
                generator = result.body_iterator
                # Get at least one item from the generator to trigger execution
                try:
                    async for item in generator:
                        break  # Just get one item to trigger execution
                except StopAsyncIteration:
                    pass

                return result

            result = asyncio.run(run_test())

            # Assert
            self.assertIsInstance(result, StreamingResponse)
            # Basic functionality test - just verify the response is correct type
            # The detailed function calls are tested in their own unit tests

    def test_summary_index_name_no_tenant_id(self):
        """
        Test summary_index_name raises exception when tenant_id is missing.
        
        This test verifies that:
        1. An exception is raised when tenant_id is None
        2. The exception message contains "Tenant ID is required"
        """
        # Execute and Assert
        async def run_test():
            with self.assertRaises(Exception) as context:
                await self.es_service.summary_index_name(
                    index_name="test_index",
                    batch_size=1000,
                    vdb_core=self.mock_vdb_core,
                    language='en',
                    model_id=1,
                    tenant_id=None  # Missing tenant_id
                )
            self.assertIn("Tenant ID is required", str(context.exception))
        
        asyncio.run(run_test())

    def test_summary_index_name_no_documents(self):
        """
        Test summary_index_name when no documents are found in index.
        
        This test verifies that:
        1. An exception is raised when document_samples is empty
        2. The exception message contains "No documents found in index"
        """
        # Mock the new Map-Reduce functions
        with patch('utils.document_vector_utils.process_documents_for_clustering') as mock_process_docs, \
             patch('utils.document_vector_utils.kmeans_cluster_documents') as mock_cluster, \
             patch('utils.document_vector_utils.summarize_clusters_map_reduce') as mock_summarize, \
             patch('utils.document_vector_utils.merge_cluster_summaries') as mock_merge:
            
            # Mock return empty document_samples
            mock_process_docs.return_value = (
                {},  # Empty document_samples
                {}  # Empty doc_embeddings
            )
            
            # Execute
            async def run_test():
                with self.assertRaises(Exception) as context:
                    result = await self.es_service.summary_index_name(
                        index_name="test_index",
                        batch_size=1000,
                        vdb_core=self.mock_vdb_core,
                        language='en',
                        model_id=1,
                        tenant_id="test_tenant"
                    )
                    # Consume the stream to trigger execution
                    generator = result.body_iterator
                    async for item in generator:
                        break
                
                self.assertIn("No documents found in index", str(context.exception))
            
            asyncio.run(run_test())

    def test_summary_index_name_runtime_error_fallback(self):
        """
        Test summary_index_name fallback when get_running_loop raises RuntimeError.
        
        This test verifies that:
        1. When get_running_loop() raises RuntimeError, get_event_loop() is used as fallback
        2. The summary generation still works correctly
        """
        # Mock the new Map-Reduce functions
        with patch('utils.document_vector_utils.process_documents_for_clustering') as mock_process_docs, \
             patch('utils.document_vector_utils.kmeans_cluster_documents') as mock_cluster, \
             patch('utils.document_vector_utils.summarize_clusters_map_reduce') as mock_summarize, \
             patch('utils.document_vector_utils.merge_cluster_summaries') as mock_merge:
            
            # Mock return values
            mock_process_docs.return_value = (
                {"doc1": {"chunks": [{"content": "test content"}]}},  # document_samples
                {"doc1": np.array([0.1, 0.2, 0.3])}  # doc_embeddings
            )
            mock_cluster.return_value = {"doc1": 0}  # clusters
            mock_summarize.return_value = {0: "Test cluster summary"}  # cluster_summaries
            mock_merge.return_value = "Final merged summary"  # final_summary
            
            # Create a mock loop with run_in_executor that returns a coroutine
            mock_loop = MagicMock()
            async def mock_run_in_executor(executor, func, *args):
                # Execute the function synchronously and return its result
                return func()
            mock_loop.run_in_executor = mock_run_in_executor
            
            # Patch asyncio functions to trigger RuntimeError fallback
            with patch('backend.services.vectordatabase_service.asyncio.get_running_loop', side_effect=RuntimeError("No running event loop")), \
                 patch('backend.services.vectordatabase_service.asyncio.get_event_loop', return_value=mock_loop) as mock_get_event_loop:
                
                # Execute
                async def run_test():
                    result = await self.es_service.summary_index_name(
                        index_name="test_index",
                        batch_size=1000,
                        vdb_core=self.mock_vdb_core,
                        language='en',
                        model_id=1,
                        tenant_id="test_tenant"
                    )
                    
                    # Consume part of the stream to trigger execution
                    generator = result.body_iterator
                    try:
                        async for item in generator:
                            break
                    except StopAsyncIteration:
                        pass
                    
                    return result
                
                result = asyncio.run(run_test())
                
                # Assert
                self.assertIsInstance(result, StreamingResponse)
                # Verify fallback was used
                mock_get_event_loop.assert_called()

    def test_summary_index_name_generator_exception(self):
        """
        Test summary_index_name handles exceptions in the generator function.
        
        This test verifies that:
        1. Exceptions in the generator are caught and streamed as error messages
        2. The error status is properly formatted
        """
        # Mock the new Map-Reduce functions
        with patch('utils.document_vector_utils.process_documents_for_clustering') as mock_process_docs, \
             patch('utils.document_vector_utils.kmeans_cluster_documents') as mock_cluster, \
             patch('utils.document_vector_utils.summarize_clusters_map_reduce') as mock_summarize, \
             patch('utils.document_vector_utils.merge_cluster_summaries') as mock_merge:
            
            # Mock return values
            mock_process_docs.return_value = (
                {"doc1": {"chunks": [{"content": "test content"}]}},  # document_samples
                {"doc1": np.array([0.1, 0.2, 0.3])}  # doc_embeddings
            )
            mock_cluster.return_value = {"doc1": 0}  # clusters
            mock_summarize.return_value = {0: "Test cluster summary"}  # cluster_summaries
            mock_merge.return_value = "Final merged summary"  # final_summary
            
            # Execute
            async def run_test():
                result = await self.es_service.summary_index_name(
                    index_name="test_index",
                    batch_size=1000,
                    vdb_core=self.mock_vdb_core,
                    language='en',
                    model_id=1,
                    tenant_id="test_tenant"
                )
                
                # Consume the stream completely
                generator = result.body_iterator
                items = []
                try:
                    async for item in generator:
                        items.append(item)
                except Exception:
                    pass
                
                return result, items
            
            result, items = asyncio.run(run_test())
            
            # Assert
            self.assertIsInstance(result, StreamingResponse)
            # Verify that items were generated (at least the completed message)
            self.assertGreater(len(items), 0)

    def test_summary_index_name_sample_count_calculation(self):
        """
        Test summary_index_name correctly calculates sample_count from batch_size.
        
        This test verifies that:
        1. sample_count is calculated as min(batch_size // 5, 200)
        2. The sample_doc_count parameter is passed correctly to process_documents_for_clustering
        """
        # Test with batch_size=1000 -> sample_count should be min(200, 200) = 200
        with patch('utils.document_vector_utils.process_documents_for_clustering') as mock_process_docs, \
             patch('utils.document_vector_utils.kmeans_cluster_documents') as mock_cluster, \
             patch('utils.document_vector_utils.summarize_clusters_map_reduce') as mock_summarize, \
             patch('utils.document_vector_utils.merge_cluster_summaries') as mock_merge:
            
            # Mock return values
            mock_process_docs.return_value = (
                {"doc1": {"chunks": [{"content": "test content"}]}},  # document_samples
                {"doc1": np.array([0.1, 0.2, 0.3])}  # doc_embeddings
            )
            mock_cluster.return_value = {"doc1": 0}  # clusters
            mock_summarize.return_value = {0: "Test cluster summary"}  # cluster_summaries
            mock_merge.return_value = "Final merged summary"  # final_summary
            
            # Execute with batch_size=1000
            async def run_test():
                result = await self.es_service.summary_index_name(
                    index_name="test_index",
                    batch_size=1000,
                    vdb_core=self.mock_vdb_core,
                    language='en',
                    model_id=1,
                    tenant_id="test_tenant"
                )
                
                # Consume part of the stream to trigger execution
                generator = result.body_iterator
                try:
                    async for item in generator:
                        break
                except StopAsyncIteration:
                    pass
                
                return result
            
            asyncio.run(run_test())
            
            # Verify sample_doc_count was called with 200 (min(1000 // 5, 200) = 200)
            self.assertTrue(mock_process_docs.called)
            call_args = mock_process_docs.call_args
            self.assertEqual(call_args.kwargs['sample_doc_count'], 200)
        
        # Test with batch_size=50 -> sample_count should be min(10, 200) = 10
        with patch('utils.document_vector_utils.process_documents_for_clustering') as mock_process_docs, \
             patch('utils.document_vector_utils.kmeans_cluster_documents') as mock_cluster, \
             patch('utils.document_vector_utils.summarize_clusters_map_reduce') as mock_summarize, \
             patch('utils.document_vector_utils.merge_cluster_summaries') as mock_merge:
            
            # Mock return values
            mock_process_docs.return_value = (
                {"doc1": {"chunks": [{"content": "test content"}]}},
                {"doc1": np.array([0.1, 0.2, 0.3])}
            )
            mock_cluster.return_value = {"doc1": 0}
            mock_summarize.return_value = {0: "Test cluster summary"}
            mock_merge.return_value = "Final merged summary"
            
            # Execute with batch_size=50
            async def run_test_small():
                result = await self.es_service.summary_index_name(
                    index_name="test_index",
                    batch_size=50,
                    vdb_core=self.mock_vdb_core,
                    language='en',
                    model_id=1,
                    tenant_id="test_tenant"
                )
                
                # Consume part of the stream to trigger execution
                generator = result.body_iterator
                try:
                    async for item in generator:
                        break
                except StopAsyncIteration:
                    pass
                
                return result
            
            asyncio.run(run_test_small())
            
            # Verify sample_doc_count was called with 10 (min(50 // 5, 200) = 10)
            self.assertTrue(mock_process_docs.called)
            call_args = mock_process_docs.call_args
            self.assertEqual(call_args.kwargs['sample_doc_count'], 10)

    def test_get_random_documents(self):
        """
        Test retrieving random documents from an index.

        This test verifies that:
        1. The method gets the total document count in the index
        2. A random sample of documents is retrieved
        3. The response contains both the total count and the sampled documents
        """
        # Setup
        self.mock_vdb_core.count_documents.return_value = 100

        search_response = {
            'hits': {
                'hits': [
                    {
                        '_id': 'doc1',
                        '_source': {"title": "Doc1", "content": "Content1"}
                    },
                    {
                        '_id': 'doc2',
                        '_source': {"title": "Doc2", "content": "Content2"}
                    }
                ]
            }
        }
        self.mock_vdb_core.search.return_value = search_response

        # Execute
        result = ElasticSearchService.get_random_documents(
            index_name="test_index",
            batch_size=10,
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(result["total"], 100)
        self.assertEqual(len(result["documents"]), 2)
        self.mock_vdb_core.count_documents.assert_called_once_with("test_index")
        self.mock_vdb_core.search.assert_called_once()

    @patch('backend.services.vectordatabase_service.update_knowledge_record')
    def test_change_summary(self, mock_update_record):
        """
        Test changing the summary of a knowledge base.

        This test verifies that:
        1. The knowledge record is updated with the new summary
        2. The response includes a success status and the updated summary
        3. The update_knowledge_record function is called with correct parameters
        """
        # Setup
        mock_update_record.return_value = True

        # Execute
        result = self.es_service.change_summary(
            index_name="test_index",
            summary_result="Test summary",
            user_id="test_user"
        )

        # Assert
        self.assertEqual(result["status"], "success")
        self.assertEqual(result["summary"], "Test summary")
        mock_update_record.assert_called_once()

    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_get_summary(self, mock_get_record):
        """
        Test retrieving the summary of a knowledge base.

        This test verifies that:
        1. The knowledge record is retrieved for the specified index
        2. The summary is extracted from the record
        3. The response includes a success status and the summary
        """
        # Setup
        mock_get_record.return_value = {
            "knowledge_describe": "Test summary"
        }

        # Execute
        result = self.es_service.get_summary(index_name="test_index")

        # Assert
        self.assertEqual(result["status"], "success")
        self.assertEqual(result["summary"], "Test summary")
        mock_get_record.assert_called_once_with({'index_name': 'test_index'})

    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_get_summary_not_found(self, mock_get_record):
        """
        Test retrieving a summary when the knowledge record doesn't exist.

        This test verifies that:
        1. When the knowledge record is not found, an exception is raised
        2. The exception has the correct status code (500)
        3. The exception message contains "Unable to get summary"
        """
        # Setup
        mock_get_record.return_value = None

        # Execute and Assert
        with self.assertRaises(Exception) as context:
            self.es_service.get_summary(index_name="test_index")

        self.assertIn("Unable to get summary", str(context.exception))

    def test_get_index_chunks_filters_fields(self):
        """
        Test chunk retrieval filters unsupported fields and reports totals.
        """
        self.mock_vdb_core.get_index_chunks.return_value = {
            "chunks": [
                {"id": "1", "content": "A", "path_or_url": "/a", "extra": "ignore"},
                {"content": "B", "create_time": "2024-01-01T00:00:00"}
            ],
            "total": 2,
            "page": None,
            "page_size": None,
        }

        result = ElasticSearchService.get_index_chunks(
            index_name="kb-index",
            vdb_core=self.mock_vdb_core
        )

        self.assertEqual(result["status"], "success")
        self.assertEqual(result["total"], 2)
        self.assertEqual(result["chunks"][0], {"id": "1", "content": "A", "path_or_url": "/a"})
        self.assertEqual(result["chunks"][1], {"content": "B", "create_time": "2024-01-01T00:00:00"})
        self.mock_vdb_core.get_index_chunks.assert_called_once_with(
            "kb-index",
            page=None,
            page_size=None,
            path_or_url=None,
        )

    def test_get_index_chunks_keeps_non_dict_entries(self):
        """
        Test chunk retrieval keeps non-dict entries unchanged.
        """
        self.mock_vdb_core.get_index_chunks.return_value = {
            "chunks": ["raw_chunk"],
            "total": 1,
            "page": 1,
            "page_size": 1,
        }

        result = ElasticSearchService.get_index_chunks(
            index_name="kb-index",
            vdb_core=self.mock_vdb_core
        )

        self.assertEqual(result["chunks"], ["raw_chunk"])
        self.assertEqual(result["total"], 1)

    def test_get_index_chunks_error(self):
        """
        Test chunk retrieval error handling.
        """
        self.mock_vdb_core.get_index_chunks.side_effect = Exception("boom")

        with self.assertRaises(Exception) as exc:
            ElasticSearchService.get_index_chunks(
                index_name="kb-index",
                vdb_core=self.mock_vdb_core
            )

        self.assertIn("Error retrieving chunks from index kb-index: boom", str(exc.exception))

    def test_create_chunk_builds_payload_and_calls_core(self):
        """
        Test create_chunk builds payload and delegates to vdb_core.create_chunk.
        """
        from types import SimpleNamespace

        self.mock_vdb_core.create_chunk.return_value = {"id": "chunk-1"}
        chunk_request = SimpleNamespace(
            chunk_id=None,
            title="My title",
            filename="file.txt",
            path_or_url="doc-1",
            content="hello world",
            metadata={"lang": "en"},
        )

        result = ElasticSearchService.create_chunk(
            index_name="kb-index",
            chunk_request=chunk_request,
            vdb_core=self.mock_vdb_core,
            user_id="user-1",
        )

        self.assertEqual(result["status"], "success")
        self.assertEqual(result["chunk_id"], "chunk-1")
        self.mock_vdb_core.create_chunk.assert_called_once()
        # create_chunk is called positionally: (index_name, chunk_payload)
        _, payload = self.mock_vdb_core.create_chunk.call_args[0]
        # Base fields
        self.assertEqual(payload["content"], "hello world")
        self.assertEqual(payload["path_or_url"], "doc-1")
        self.assertEqual(payload["filename"], "file.txt")
        self.assertEqual(payload["title"], "My title")
        self.assertEqual(payload["created_by"], "user-1")
        # Metadata merged
        self.assertEqual(payload["lang"], "en")
        self.assertIn("id", payload)

    def test_update_chunk_builds_payload_and_calls_core(self):
        """
        Test update_chunk builds update payload and delegates to vdb_core.update_chunk.
        """

        class DummyUpdate:
            def __init__(self, **fields):
                self._fields = fields
                # Expose metadata attribute like real Pydantic model
                self.metadata = fields.get("metadata")

            def dict(self, exclude_unset=True, exclude=None):
                data = dict(self._fields)
                if exclude:
                    for key in exclude:
                        data.pop(key, None)
                return data

        self.mock_vdb_core.update_chunk.return_value = {"id": "chunk-1"}
        chunk_request = DummyUpdate(
            content="updated",
            filename="updated.txt",
            metadata={"lang": "en"},
        )

        result = ElasticSearchService.update_chunk(
            index_name="kb-index",
            chunk_id="chunk-1",
            chunk_request=chunk_request,
            vdb_core=self.mock_vdb_core,
            user_id="user-1",
        )

        self.assertEqual(result["status"], "success")
        self.assertEqual(result["chunk_id"], "chunk-1")
        self.mock_vdb_core.update_chunk.assert_called_once_with(
            "kb-index", "chunk-1", ANY
        )

    def test_delete_chunk_success(self):
        """
        Test delete_chunk returns success when vdb_core.delete_chunk is True.
        """
        self.mock_vdb_core.delete_chunk.return_value = True

        result = ElasticSearchService.delete_chunk(
            index_name="kb-index",
            chunk_id="chunk-1",
            vdb_core=self.mock_vdb_core,
        )

        self.assertEqual(result["status"], "success")
        self.assertEqual(result["chunk_id"], "chunk-1")
        self.mock_vdb_core.delete_chunk.assert_called_once_with(
            "kb-index", "chunk-1"
        )

    def test_delete_chunk_not_found_raises_value_error(self):
        """
        Test delete_chunk raises ValueError when vdb_core.delete_chunk returns False.
        """
        self.mock_vdb_core.delete_chunk.return_value = False

        with self.assertRaises(Exception) as exc:
            ElasticSearchService.delete_chunk(
                index_name="kb-index",
                chunk_id="missing",
                vdb_core=self.mock_vdb_core,
            )

        self.assertIn(
            "Error deleting chunk: Chunk missing not found in index kb-index", str(exc.exception))

    @patch('backend.services.vectordatabase_service.get_knowledge_info_by_tenant_id')
    @patch('fastapi.Response')
    def test_list_indices_success_status_200(self, mock_response, mock_get_knowledge):
        """
        Test list_indices method returns status code 200 on success.

        This test verifies that:
        1. The list_indices method successfully retrieves indices
        2. The response is a dictionary containing the expected data
        3. The method completes without raising exceptions, implying a 200 status code
        """
        # Setup
        self.mock_vdb_core.get_user_indices.return_value = ["index1", "index2"]
        mock_response.status_code = 200
        mock_get_knowledge.return_value = [
            {"index_name": "index1", "embedding_model_name": "test-model"},
            {"index_name": "index2", "embedding_model_name": "test-model"}
        ]

        # Execute
        result = ElasticSearchService.list_indices(
            pattern="*",
            include_stats=False,
            tenant_id="test_tenant",  # Now required parameter
            user_id="test_user",      # New required parameter
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(len(result["indices"]), 2)
        self.assertEqual(result["count"], 2)
        # Verify no exception is raised, implying 200 status code
        self.assertIsInstance(result, dict)  # Success response is a dictionary
        self.mock_vdb_core.get_user_indices.assert_called_once_with("*")
        mock_get_knowledge.assert_called_once_with(tenant_id="test_tenant")

    def test_health_check_success_status_200(self):
        """
        Test health_check method returns status code 200 on success.

        This test verifies that:
        1. The health_check method successfully checks Elasticsearch health
        2. The response is a dictionary with a "healthy" status
        3. The method completes without raising exceptions, implying a 200 status code
        """
        # Setup
        self.mock_vdb_core.get_user_indices.return_value = ["index1", "index2"]

        # Execute
        result = ElasticSearchService.health_check(vdb_core=self.mock_vdb_core)

        # Assert
        self.assertEqual(result["status"], "healthy")
        self.assertEqual(result["elasticsearch"], "connected")
        # Verify successful response status - 200
        self.assertIsInstance(result, dict)  # Success response is a dictionary

    def test_get_random_documents_success_status_200(self):
        """
        Test get_random_documents method returns status code 200 on success.

        This test verifies that:
        1. The get_random_documents method successfully retrieves random documents
        2. The response contains the expected data structure with total and documents
        3. The method completes without raising exceptions, implying a 200 status code
        """
        # Setup
        self.mock_vdb_core.count_documents.return_value = 100

        search_response = {
            'hits': {
                'hits': [
                    {
                        '_id': 'doc1',
                        '_source': {"title": "Doc1", "content": "Content1"}
                    }
                ]
            }
        }
        self.mock_vdb_core.search.return_value = search_response

        # Execute
        result = ElasticSearchService.get_random_documents(
            index_name="test_index",
            batch_size=10,
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(result["total"], 100)
        self.assertEqual(len(result["documents"]), 1)
        # Verify successful response status - 200
        self.assertIsInstance(result, dict)  # Success response is a dictionary
        self.assertIn("total", result)
        self.assertIn("documents", result)

    def test_semantic_search_success_status_200(self):
        """
        Test semantic_search method returns status code 200 on success.

        This test verifies that:
        1. The semantic_search method successfully performs a search
        2. The response contains the expected search results
        3. The method completes without raising exceptions, implying a 200 status code
        """
        # Setup
        search_request = MagicMock()
        search_request.index_names = ["test_index"]
        search_request.query = "valid query"
        search_request.top_k = 10

        self.mock_vdb_core.semantic_search.return_value = [
            {
                "document": {"title": "Doc1", "content": "Content1"},
                "score": 0.85,
                "index": "test_index"
            }
        ]

        # Execute
        result = ElasticSearchService.semantic_search(
            request=search_request,
            vdb_core=self.mock_vdb_core
        )

        # Assert
        self.assertEqual(len(result["results"]), 1)
        # Verify successful response status - 200
        self.assertIsInstance(result, dict)
        self.assertIn("results", result)
        self.assertIn("total", result)
        self.assertIn("query_time_ms", result)
        self.mock_vdb_core.semantic_search.assert_called_once_with(
            index_names=["test_index"], query="valid query", top_k=10
        )

    def test_vectorize_documents_success_status_200(self):
        """
        Test vectorize_documents method returns status code 200 on success.

        This test verifies that:
        1. The vectorize_documents method successfully indexes multiple documents
        2. The response indicates success and correct document counts
        3. The method completes without raising exceptions, implying a 200 status code
        """
        # Setup
        self.mock_vdb_core.check_index_exists.return_value = True
        self.mock_vdb_core.vectorize_documents.return_value = 3
        mock_embedding_model = MagicMock()
        mock_embedding_model.model = "test-model"

        test_data = [
            {
                "metadata": {"title": "Test1", "languages": ["en"]},
                "path_or_url": "path1",
                "content": "Content1"
            },
            {
                "metadata": {"title": "Test2", "languages": ["zh"]},
                "path_or_url": "path2",
                "content": "Content2"
            },
            {
                "metadata": {"title": "Test3", "languages": ["fr"]},
                "path_or_url": "path3",
                "content": "Content3"
            }
        ]

        # Execute
        result = ElasticSearchService.index_documents(
            index_name="test_index",
            data=test_data,
            vdb_core=self.mock_vdb_core,
            embedding_model=mock_embedding_model
        )

        # Assert
        self.assertTrue(result["success"])
        self.assertEqual(result["total_indexed"], 3)
        self.assertEqual(result["total_submitted"], 3)
        # Verify successful response status - 200
        self.assertIsInstance(result, dict)
        self.assertIn("success", result)
        self.assertTrue(result["success"])

    @patch('backend.services.vectordatabase_service.delete_file')
    def test_delete_documents_success_status_200(self, mock_delete_file):
        """
        Test delete_documents method returns status code 200 on success.

        This test verifies that:
        1. The delete_documents method successfully deletes documents
        2. The response indicates success
        3. The method completes without raising exceptions, implying a 200 status code
        """
        # Setup
        self.mock_vdb_core.delete_documents.return_value = 5
        # Configure delete_file to return a success response
        mock_delete_file.return_value = {"success": True, "object_name": "test_path"}

        # Execute
        result = ElasticSearchService.delete_documents(
            index_name="test_index",
            path_or_url="test_path",
            vdb_core=self.mock_vdb_core
        )

        # Assert
        # Verify successful response status - 200
        self.assertIsInstance(result, dict)
        self.assertEqual(result["status"], "success")
        self.assertEqual(result["deleted_minio"], True)
        # Verify that delete_documents was called with correct parameters
        self.mock_vdb_core.delete_documents.assert_called_once_with(
            "test_index", "test_path")
        # Verify that delete_file was called with the correct path
        mock_delete_file.assert_called_once_with("test_path")

    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_get_summary_success_status_200(self, mock_get_record):
        """
        Test get_summary method returns status code 200 on success.

        This test verifies that:
        1. The get_summary method successfully retrieves a knowledge base summary
        2. The response indicates success and contains the summary
        3. The method completes without raising exceptions, implying a 200 status code
        """
        # Setup
        mock_get_record.return_value = {
            "knowledge_describe": "This is a test summary for knowledge base"
        }

        # Execute
        result = self.es_service.get_summary(index_name="test_index")

        # Assert
        self.assertEqual(result["status"], "success")
        self.assertEqual(result["summary"],
                         "This is a test summary for knowledge base")
        # Verify successful response status - 200
        self.assertIsInstance(result, dict)
        self.assertEqual(result["status"], "success")
        mock_get_record.assert_called_once_with({'index_name': 'test_index'})

    @patch('backend.services.vectordatabase_service.get_redis_service')
    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_check_kb_exist_orphan_in_es(self, mock_get_knowledge, mock_get_redis_service):
        """Test handling of orphaned knowledge base existing only in Elasticsearch."""
        # Setup: ES index exists, PG record missing
        self.mock_vdb_core.check_index_exists.return_value = True
        mock_get_knowledge.return_value = None

        # Mock Redis service
        mock_redis_service = MagicMock()
        mock_redis_service.delete_knowledgebase_records.return_value = {
            "total_deleted": 1}
        mock_get_redis_service.return_value = mock_redis_service

        # Execute
        result = check_knowledge_base_exist_impl(
            index_name="test_index",
            vdb_core=self.mock_vdb_core,
            user_id="test_user",
            tenant_id="tenant1"
        )

        # Assert
        self.mock_vdb_core.delete_index.assert_called_once_with("test_index")
        mock_redis_service.delete_knowledgebase_records.assert_called_once_with(
            "test_index")
        self.assertEqual(result["status"], "error_cleaning_orphans")
        self.assertEqual(result["action"], "cleaned_es")

    @patch('backend.services.vectordatabase_service.delete_knowledge_record')
    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_check_kb_exist_orphan_in_pg(self, mock_get_knowledge, mock_delete_record):
        """Test handling of orphaned knowledge base existing only in PostgreSQL."""
        # Setup: ES index missing, PG record exists
        self.mock_vdb_core.check_index_exists.return_value = False
        mock_get_knowledge.return_value = {
            "index_name": "test_index", "tenant_id": "tenant1"}
        mock_delete_record.return_value = True

        # Execute
        result = check_knowledge_base_exist_impl(
            index_name="test_index",
            vdb_core=self.mock_vdb_core,
            user_id="test_user",
            tenant_id="tenant1"
        )

        # Assert
        mock_delete_record.assert_called_once()
        self.assertEqual(result["status"], "error_cleaning_orphans")
        self.assertEqual(result["action"], "cleaned_pg")

    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_check_kb_exist_available(self, mock_get_knowledge):
        """Test knowledge base name availability when neither ES nor PG has the record."""
        # Setup: ES index missing, PG record missing
        self.mock_vdb_core.check_index_exists.return_value = False
        mock_get_knowledge.return_value = None

        # Execute
        result = check_knowledge_base_exist_impl(
            index_name="test_index",
            vdb_core=self.mock_vdb_core,
            user_id="test_user",
            tenant_id="tenant1"
        )

        # Assert
        self.assertEqual(result["status"], "available")

    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_check_kb_exist_exists_in_tenant(self, mock_get_knowledge):
        """Test detection when knowledge base exists within the same tenant."""
        # Setup: ES index exists, PG record exists with same tenant_id
        self.mock_vdb_core.check_index_exists.return_value = True
        mock_get_knowledge.return_value = {
            "index_name": "test_index", "tenant_id": "tenant1"}

        # Execute
        result = check_knowledge_base_exist_impl(
            index_name="test_index",
            vdb_core=self.mock_vdb_core,
            user_id="test_user",
            tenant_id="tenant1"
        )

        # Assert
        self.assertEqual(result["status"], "exists_in_tenant")

    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_check_kb_exist_exists_in_other_tenant(self, mock_get_knowledge):
        """Test detection when knowledge base exists in a different tenant."""
        # Setup: ES index exists, PG record exists with different tenant_id
        self.mock_vdb_core.check_index_exists.return_value = True
        mock_get_knowledge.return_value = {
            "index_name": "test_index", "tenant_id": "other_tenant"}

        # Execute
        result = check_knowledge_base_exist_impl(
            index_name="test_index",
            vdb_core=self.mock_vdb_core,
            user_id="test_user",
            tenant_id="tenant1"
        )

        # Assert
        self.assertEqual(result["status"], "exists_in_other_tenant")

    @patch('backend.services.vectordatabase_service.get_redis_service')
    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_check_kb_exist_orphan_in_es_redis_failure(self, mock_get_knowledge, mock_get_redis_service):
        """Test orphan ES case when Redis cleanup raises an exception."""
        # Setup: ES index exists, PG record missing
        self.mock_vdb_core.check_index_exists.return_value = True
        mock_get_knowledge.return_value = None

        # Mock Redis service that raises an exception
        mock_redis_service = MagicMock()
        mock_redis_service.delete_knowledgebase_records.side_effect = Exception(
            "Redis error")
        mock_get_redis_service.return_value = mock_redis_service

        # Execute
        result = check_knowledge_base_exist_impl(
            index_name="test_index",
            vdb_core=self.mock_vdb_core,
            user_id="test_user",
            tenant_id="tenant1"
        )

        # Assert: ES index deletion attempted, Redis cleanup attempted and exception handled
        self.mock_vdb_core.delete_index.assert_called_once_with("test_index")
        mock_redis_service.delete_knowledgebase_records.assert_called_once_with(
            "test_index")
        self.assertEqual(result["status"], "error_cleaning_orphans")
        self.assertEqual(result["action"], "cleaned_es")

    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_check_kb_exist_orphan_in_es_delete_failure(self, mock_get_knowledge):
        """Test failure when deleting orphan ES index raises an exception."""
        # Setup: ES index exists, PG record missing, delete_index raises
        self.mock_vdb_core.check_index_exists.return_value = True
        mock_get_knowledge.return_value = None
        self.mock_vdb_core.delete_index.side_effect = Exception(
            "Delete index failed")

        # Execute
        result = check_knowledge_base_exist_impl(
            index_name="test_index",
            vdb_core=self.mock_vdb_core,
            user_id="test_user",
            tenant_id="tenant1"
        )

        # Assert
        self.mock_vdb_core.delete_index.assert_called_once_with("test_index")
        self.assertEqual(result["status"], "error_cleaning_orphans")
        self.assertTrue(result.get("error"))

    @patch('backend.services.vectordatabase_service.delete_knowledge_record')
    @patch('backend.services.vectordatabase_service.get_knowledge_record')
    def test_check_kb_exist_orphan_in_pg_delete_failure(self, mock_get_knowledge, mock_delete_record):
        """Test failure when deleting orphan PG record raises an exception."""
        # Setup: ES index missing, PG record exists, deletion raises
        self.mock_vdb_core.check_index_exists.return_value = False
        mock_get_knowledge.return_value = {
            "index_name": "test_index", "tenant_id": "tenant1"}
        mock_delete_record.side_effect = Exception("Delete PG record failed")

        # Execute
        result = check_knowledge_base_exist_impl(
            index_name="test_index",
            vdb_core=self.mock_vdb_core,
            user_id="test_user",
            tenant_id="tenant1"
        )

        # Assert
        mock_delete_record.assert_called_once()
        self.assertEqual(result["status"], "error_cleaning_orphans")
        self.assertTrue(result.get("error"))

    # Note: generate_knowledge_summary_stream function has been removed
    # These tests are no longer relevant as the function was replaced with summary_index_name

    def test_get_vdb_core(self):
        """
        Test get_vdb_core function returns the elastic_core instance.

        This test verifies that:
        1. The get_vdb_core function returns the correct elastic_core instance
        2. The function is properly imported and accessible
        """
        from backend.services.vectordatabase_service import get_vector_db_core

        # Execute
        result = get_vector_db_core()

        # Assert
        self.assertIsNotNone(result)
        # The result should be the elastic_core instance
        self.assertTrue(hasattr(result, 'client'))

    @patch('backend.services.vectordatabase_service.tenant_config_manager')
    def test_get_embedding_model_embedding_type(self, mock_tenant_config_manager):
        """
        Test get_embedding_model with embedding model type.

        This test verifies that:
        1. When model_type is "embedding", OpenAICompatibleEmbedding is returned
        2. The correct parameters are passed to the embedding model
        """
        # Setup
        mock_config = {
            "model_type": "embedding",
            "api_key": "test_api_key",
            "base_url": "https://test.api.com",
            "model_name": "test-model",
            "max_tokens": 1024
        }
        mock_tenant_config_manager.get_model_config.return_value = mock_config

        # Stop the mock from setUp to test the real function
        self.get_embedding_model_patcher.stop()

        try:
            with patch('backend.services.vectordatabase_service.OpenAICompatibleEmbedding') as mock_embedding_class, \
                    patch('backend.services.vectordatabase_service.get_model_name_from_config') as mock_get_model_name:
                mock_embedding_instance = MagicMock()
                mock_embedding_class.return_value = mock_embedding_instance
                mock_get_model_name.return_value = "test-model"

                # Execute - now we can call the real function
                from backend.services.vectordatabase_service import get_embedding_model
                result = get_embedding_model("test_tenant")

                # Assert
                self.assertEqual(result, mock_embedding_instance)
                mock_tenant_config_manager.get_model_config.assert_called_once_with(
                    key="EMBEDDING_ID", tenant_id="test_tenant")
                mock_embedding_class.assert_called_once_with(
                    api_key="test_api_key",
                    base_url="https://test.api.com",
                    model_name="test-model",
                    embedding_dim=1024
                )
        finally:
            # Restart the mock for other tests
            self.get_embedding_model_patcher.start()

    @patch('backend.services.vectordatabase_service.tenant_config_manager')
    def test_get_embedding_model_multi_embedding_type(self, mock_tenant_config_manager):
        """
        Test get_embedding_model with multi_embedding model type.

        This test verifies that:
        1. When model_type is "multi_embedding", JinaEmbedding is returned
        2. The correct parameters are passed to the embedding model
        """
        # Setup
        mock_config = {
            "model_type": "multi_embedding",
            "api_key": "test_api_key",
            "base_url": "https://test.api.com",
            "model_name": "test-model",
            "max_tokens": 2048
        }
        mock_tenant_config_manager.get_model_config.return_value = mock_config

        # Stop the mock from setUp to test the real function
        self.get_embedding_model_patcher.stop()

        try:
            with patch('backend.services.vectordatabase_service.JinaEmbedding') as mock_embedding_class, \
                    patch('backend.services.vectordatabase_service.get_model_name_from_config') as mock_get_model_name:
                mock_embedding_instance = MagicMock()
                mock_embedding_class.return_value = mock_embedding_instance
                mock_get_model_name.return_value = "test-model"

                # Execute - now we can call the real function
                from backend.services.vectordatabase_service import get_embedding_model
                result = get_embedding_model("test_tenant")

                # Assert
                self.assertEqual(result, mock_embedding_instance)
                mock_tenant_config_manager.get_model_config.assert_called_once_with(
                    key="EMBEDDING_ID", tenant_id="test_tenant")
                mock_embedding_class.assert_called_once_with(
                    api_key="test_api_key",
                    base_url="https://test.api.com",
                    model_name="test-model",
                    embedding_dim=2048
                )
        finally:
            # Restart the mock for other tests
            self.get_embedding_model_patcher.start()

    @patch('backend.services.vectordatabase_service.tenant_config_manager')
    def test_get_embedding_model_unknown_type(self, mock_tenant_config_manager):
        """
        Test get_embedding_model with unknown model type.

        This test verifies that:
        1. When model_type is neither "embedding" nor "multi_embedding", None is returned
        2. The function handles unknown model types gracefully
        """
        # Setup
        mock_config = {
            "model_type": "unknown_type",
            "api_key": "test_api_key",
            "base_url": "https://test.api.com",
            "model_name": "test-model",
            "max_tokens": 1024
        }
        mock_tenant_config_manager.get_model_config.return_value = mock_config

        # Stop the mock from setUp to test the real function
        self.get_embedding_model_patcher.stop()

        try:
            # Execute - now we can call the real function
            from backend.services.vectordatabase_service import get_embedding_model
            result = get_embedding_model("test_tenant")

            # Assert
            self.assertIsNone(result)
            mock_tenant_config_manager.get_model_config.assert_called_once_with(
                key="EMBEDDING_ID", tenant_id="test_tenant")
        finally:
            # Restart the mock for other tests
            self.get_embedding_model_patcher.start()

    @patch('backend.services.vectordatabase_service.tenant_config_manager')
    def test_get_embedding_model_empty_type(self, mock_tenant_config_manager):
        """
        Test get_embedding_model with empty model type.

        This test verifies that:
        1. When model_type is empty string, None is returned
        2. The function handles empty model types gracefully
        """
        # Setup
        mock_config = {
            "model_type": "",
            "api_key": "test_api_key",
            "base_url": "https://test.api.com",
            "model_name": "test-model",
            "max_tokens": 1024
        }
        mock_tenant_config_manager.get_model_config.return_value = mock_config

        # Stop the mock from setUp to test the real function
        self.get_embedding_model_patcher.stop()

        try:
            # Execute - now we can call the real function
            from backend.services.vectordatabase_service import get_embedding_model
            result = get_embedding_model("test_tenant")

            # Assert
            self.assertIsNone(result)
            mock_tenant_config_manager.get_model_config.assert_called_once_with(
                key="EMBEDDING_ID", tenant_id="test_tenant")
        finally:
            # Restart the mock for other tests
            self.get_embedding_model_patcher.start()

    @patch('backend.services.vectordatabase_service.tenant_config_manager')
    def test_get_embedding_model_missing_type(self, mock_tenant_config_manager):
        """
        Test get_embedding_model with missing model type.

        This test verifies that:
        1. When model_type is missing from config, None is returned
        2. The function handles missing model types gracefully
        """
        # Setup
        mock_config = {
            "api_key": "test_api_key",
            "base_url": "https://test.api.com",
            "model_name": "test-model",
            "max_tokens": 1024
        }
        mock_tenant_config_manager.get_model_config.return_value = mock_config

        # Stop the mock from setUp to test the real function
        self.get_embedding_model_patcher.stop()

        try:
            # Execute - now we can call the real function
            from backend.services.vectordatabase_service import get_embedding_model
            result = get_embedding_model("test_tenant")

            # Assert
            self.assertIsNone(result)
            mock_tenant_config_manager.get_model_config.assert_called_once_with(
                key="EMBEDDING_ID", tenant_id="test_tenant")
        finally:
            # Restart the mock for other tests
            self.get_embedding_model_patcher.start()


if __name__ == '__main__':
    unittest.main()

